"""
python 项目3 : 万能的xml
自动构建网站 根据一份xml文件,生成对应的目录机构的网站
首先我们要解析这个xml文件，python解析xml和在java中一样，有两种方式，SAX和DOM，
两种处理方式不同点在于速度和范围，前者讲究的是效率，每次只处理文档的一小部分，快速而能有效的利用内存，
后者是相反的处理方式，先把所有的文档载入到内存，然后再进行处理，速度比较慢，也比较消耗内存，唯一的好处就是可以操作整个文档。

在python中使用sax方式处理xml要先引入xml.sax中的parse函数，
还有xml.sax.handler中的ContentHandler，后面的这个类是要和parse函数来配合使用的。
使用方式如下：
 parse('xxx.xml',xxxHandler),这里面的xxxHandler要继承上面的ContentHandler，
 不过只要继承就行，不需要有所作为。 然后这个parse函数在处理xml文件的时候，
 会调用xxxHandler中的startElement函数和endElement函数来一个xml中的标签的开始和结束，
 中间的过程使用一个名为characters的函数来处理标签内部的所有字符串。

 website.xml文件，分析其结构，只有两个节点：page和directory，很明显page表示一个页面，directory表示一个目录。

 处理这个xml文件的思路就变的清晰了。读取xml文件的每一个节点，然后判断是page还是directory如果是page则创建html页面，
 然后把节点中的内容写到文件里。如果遇到directory就创建一个文件夹，然后再处理其内部的page节点
"""
from xml.sax.handler import ContentHandler
from xml.sax import parse
import os


class Dispatcher(ContentHandler):
    """
    一个用于分发XML元素处理事件的类，继承自ContentHandler。
    它根据元素名称动态调用相应的处理方法。
    """

    def dispatch(self, prefix, name, attrs=None):
        """
        根据前缀和元素名称分发处理方法。

        参数:
            prefix (str): 方法前缀，如'start'或'end'。
            name (str): XML元素的名称。
            attrs (xml.sax.xmlreader.AttributesImpl, 可选): 元素的属性列表，默认为None。
        """
        methodName = prefix + name.capitalize()
        defaultName = 'default' + prefix.capitalize()
        method = getattr(self, methodName, None)
        if callable(method):
            # 将AttributesImpl转换为字典形式
            if attrs is not None:
                attr_dict = {}
                for attr_name in attrs.getNames():
                    attr_dict[attr_name] = attrs.getValue(attr_name)
                method(name, attr_dict)
            else:
                method(name)
        else:
            method = getattr(self, defaultName, None)
            if callable(method):
                # 将AttributesImpl转换为字典形式
                if attrs is not None:
                    attr_dict = {}
                    for attr_name in attrs.getNames():
                        attr_dict[attr_name] = attrs.getValue(attr_name)
                    method(name, attr_dict)
                else:
                    method(name)

    def startElement(self, name, attrs):
        """
        处理XML开始元素事件。

        参数:
            name (str): 元素名称。
            attrs (xml.sax.xmlreader.AttributesImpl): 元素属性。
        """
        self.dispatch('start', name, attrs)

    def endElement(self, name):
        """
        处理XML结束元素事件。

        参数:
            name (str): 元素名称。
        """
        self.dispatch('end', name)


class WebsiteConstructor(Dispatcher):
    """
    构建网站结构的类，继承自Dispatcher和ContentHandler。
    用于解析XML并生成对应的HTML文件结构。

    属性:
        passthrough (bool): 控制是否将内容写入输出文件。
        directory (list): 当前处理的目录路径栈。
        out (file): 当前正在写入的文件对象。
        css_files (list): 要链接到页面的CSS文件列表。
    """
    passthrough = False

    def __init__(self, directory):
        """
        初始化WebsiteConstructor实例。

        参数:
            directory (str): 网站根目录路径。
        """
        super().__init__()
        self.directory = [directory]
        self.css_files = []  # 存储CSS文件路径
        self.ensureDirectory()

    def ensureDirectory(self):
        """
        确保当前目录路径存在，如果不存在则创建该目录。
        """
        path = os.path.join(*self.directory)
        print(path)
        print('------------')
        if not os.path.isdir(path):
            os.makedirs(path)

    def characters(self, chars):
        """
        处理XML文本内容。

        参数:
            chars (str): 文本内容。
        """
        if self.passthrough:
            self.out.write(chars)

    def defaultStart(self, name, attrs):
        """
        默认的开始元素处理方法，用于写入HTML开始标签。

        参数:
            name (str): 元素名称。
            attrs (dict): 元素属性字典。
        """
        if self.passthrough:
            self.out.write('<' + name)
            for key, val in attrs.items():
                self.out.write(' %s="%s"' % (key, val))
            self.out.write('>')

    def defaultEnd(self, name):
        """
        默认的结束元素处理方法，用于写入HTML结束标签。

        参数:
            name (str): 元素名称。
        """
        if self.passthrough:
            self.out.write('</%s>' % name)

    def startDirectory(self, name, attrs):
        """
        处理目录开始元素，进入子目录。

        参数:
            name (str): 元素名称。
            attrs (dict): 目录元素的属性，应包含'name'键。
        """
        self.directory.append(attrs['name'])
        self.ensureDirectory()

    def endDirectory(self, name):
        """
        处理目录结束元素，返回上级目录。

        参数:
            name (str): 元素名称。
        """
        print('endDirectory')
        self.directory.pop()

    def startPage(self, name, attrs):
        """
        处理页面开始元素，创建新的HTML文件。

        参数:
            name (str): 元素名称。
            attrs (dict): 页面元素的属性，应包含'name'和'title'键。
        """
        print('startPage')
        filename = os.path.join(*self.directory + [attrs['name'] + '.html'])
        self.out = open(filename, 'w')
        self.writeHeader(attrs['title'], attrs.get('css', None))
        self.passthrough = True

    def endPage(self, name):
        """
        处理页面结束元素，关闭当前HTML文件。

        参数:
            name (str): 元素名称。
        """
        print('endPage')
        self.passthrough = False
        self.writeFooter()
        self.out.close()

    def startStylesheet(self, name, attrs):
        """
        处理样式表开始元素，创建CSS文件。

        参数:
            name (str): 元素名称。
            attrs (dict): 样式表元素的属性，应包含'href'键。
        """
        # 创建CSS目录（如果不存在）
        css_dir = os.path.join(*self.directory, 'css')
        if not os.path.isdir(css_dir):
            os.makedirs(css_dir)

        # 创建CSS文件
        css_filename = os.path.join(css_dir, attrs['href'])
        with open(css_filename, 'w') as css_file:
            # 可以在这里写入默认的CSS内容，或留空让用户自己添加
            css_file.write("/* 自动创建的CSS文件 */\n")
            css_file.write("body {\n  font-family: Arial, sans-serif;\n}\n")

        print(f'Created CSS file: {css_filename}')

    def writeHeader(self, title, css_file=None):
        """
        写入HTML文件头部信息。

        参数:
            title (str): 页面标题。
            css_file (str, 可选): 要链接的CSS文件路径。
        """
        self.out.write('<!DOCTYPE html>\n')
        self.out.write('<html>\n <head>\n')
        self.out.write('   <meta charset="UTF-8">\n')
        self.out.write('   <title>')
        self.out.write(title)
        self.out.write('</title>\n')

        # 添加CSS链接
        if css_file:
            self.out.write(f'   <link rel="stylesheet" href="{css_file}">\n')
        elif self.css_files:
            # 如果有全局CSS文件，则链接它们
            for css in self.css_files:
                self.out.write(f'   <link rel="stylesheet" href="{css}">\n')

        self.out.write(' </head>\n  <body>\n')

    def writeFooter(self):
        """
        写入HTML文件尾部信息。
        """
        self.out.write('\n </body>\n</html>\n')


if __name__ == '__main__':
    parse('website.xml', WebsiteConstructor('public_html'))
